// Written by Gideon King

// Caveats and options for improvement
// - If a target or action changes due to a program update, the affected menu items won't work
// - Could be extended to allow user re-ordering of menus - you'd need to save the whole menu structure though,
//   and watch out for program updates.
// - You could ask the users to name their custom menus when you create new ones.
 
//
// Source file      : DraggableMenu.m
// Created by       : gideon@berd
// Created on       : Fri Sep 8 16:18:54 NZST 1995
// RCS File         : $Source: /Ramoth/Black.Albatross/CVS/CustomMenu/CustomMenu.subproj/DraggableMenu.m,v $
// Last modified    : $Date: 1995/09/10 22:38:07 $
// Last modified by : $Author: gideon $
// Current Revision : $Revision: 1.1.1.1 $
//
 
static const char RCSId[] = "$Id: DraggableMenu.m,v 1.1.1.1 1995/09/10 22:38:07 gideon Exp $";
 

#import "DraggableMenu.h"
#import "CustomMenu.h"

// My private pasteboard for menu cells
const char *MHMenuCellPboardType = "MenuHack Menu Cell version 1.0";

// This has to be defined here so we don't have an instance variable, and therefore can't poseAs menu
BOOL __saveAndRestoreFlag__ = YES;
BOOL __haveUnarchivedMenu__ = NO;

@implementation DraggableMenu

- (const char *)version
{
	return RCSId;
}

- setSaveAndRestore:(BOOL)yesNo
{
	__saveAndRestoreFlag__ = yesNo;
	return self;
}

- (BOOL)getSaveAndRestore
{
	return __saveAndRestoreFlag__;
}

- awakeFromNib
{
	char fileName[MAXPATHLEN+1];
	NXStream *stream;
	
	if (__saveAndRestoreFlag__ && !__haveUnarchivedMenu__)
	{
		__haveUnarchivedMenu__ = YES;
		// Load the custom menus from file
		sprintf(fileName, "%s/.NeXT/%s.customMenus", NXHomeDirectory(), [NXApp appName]);	

		stream = NXMapFile(fileName, NX_READONLY);
		
		if (stream)
		{
			NX_DURING
				NXTypedStream *ts = NXOpenTypedStream(stream, NX_READONLY);
				if (ts)
				{
					id mainItemList = [[NXApp mainMenu] itemList];
					int i, count;
					NXReadType(ts, "i", &count);
					for (i = 0; i < count; i++)
					{
						id newItem = NXReadObject(ts);
						[mainItemList insertRowAt:0];
						[mainItemList putCell:newItem at:0 :0];
					}
					[[NXApp mainMenu] setItemList:mainItemList];
					[[NXApp mainMenu] display];
					NXCloseTypedStream(ts);
				}
				else
				{
					fprintf(stderr, "Error opening %s for reading.\n", fileName);
				}
				NXCloseMemory(stream, NX_FREEBUFFER);			
			NX_HANDLER
				fprintf(stderr, "Error reading file %s.\n", fileName);
			NX_ENDHANDLER
		}
	}

	return self;
}

// This is a nasty hack - because we are using poseAs to take over from Menu, we can't add any new methods
// that you can connect to in IB, so I've taken over the performClose mathod. Oh well, I guess the whole thing
// is a nasty hack anyway, so who cares???
- performClose:sender
{
	char fileName[MAXPATHLEN+1];
	NXTypedStream *ts;

	if (strcmp([[sender selectedCell] title], "Quit") == 0)
	{
		if (__saveAndRestoreFlag__)
		{
			// Write the custom menus to file
			sprintf(fileName, "%s/.NeXT/%s.customMenus", NXHomeDirectory(), [NXApp appName]);
		
			ts = NXOpenTypedStreamForFile(fileName, NX_WRITEONLY);
			if (ts)
			{
				NX_DURING
					int i, count = 0;
					id mainItemList = [[NXApp mainMenu] itemList];
					
					// First go through and find out how many menu cells we have to archive	
					for (i = 0; i < [mainItemList cellCount]; i++)
					{
						id thisCell = [mainItemList cellAt:i :0];
						// We have given the menus we have added a tag of 42131 to identify them
						if(strstr([thisCell title], "Custom Menu") && ([thisCell tag] == 42131))
						{
							count++;
						}
					}
					
					NXWriteType(ts, "i", &count);
					
					// Now archive the cells in reverse to make them easier to unarchive
					for (i = count-1; i >= 0; i--)
					{
						NXWriteRootObject(ts, [mainItemList cellAt:i :0]);
					}
					NXCloseTypedStream(ts);
				NX_HANDLER
					fprintf(stderr, "Error writing file %s.\n", fileName);
				NX_ENDHANDLER
			}
			else
			{
				fprintf(stderr, "Error opening %s for writing.\n", fileName);
			}
		}
		
		return [NXApp terminate:sender];
	}
	
	return [super performClose:sender];
}

// Pick up an Alternate-drag of a menu item, and start a dragging session
- mouseDown:(NXEvent *)theEvent
{		
	// Only do the dragging if an Alternate key is down
	if (theEvent->flags & NX_ALTERNATEMASK)
	{
		NXImage *theImage;
		id theCell = [matrix selectedCell];
		int theRow = [matrix selectedRow];
		NXBitmapImageRep *newRep = nil;
		
   		NXPoint zero = {0.0, 0.0};
		NXPoint offset;
		id 	thePboard;
		NXRect myRect;
		NXRect matrixFrame;

		NXStream *stream;
		NXTypedStream *ts;
		
		static NXCursor *cursor = nil;
		NXPoint spot;
		
		// Work out the size and position of the menu cell.
		[matrix getCellFrame:&myRect at:theRow :0];

		theImage = [[NXImage alloc] initSize:&(myRect.size)];
		
		// Draw the button into the imagerep
		[matrix lockFocus];
		newRep = [[NXBitmapImageRep alloc] initData:NULL fromRect:&myRect];
		[matrix unlockFocus];
		
		[matrix getFrame:&matrixFrame];

		[theImage useRepresentation:newRep];
		offset.x = 0;
		offset.y = theEvent->location.y + 
			((NX_HEIGHT(&matrixFrame) - NX_Y(&myRect)) - theEvent->location.y)
			- NX_HEIGHT(&myRect);

		// get the Pboard and put the menu cell on to it.
		thePboard = [Pasteboard newName:NXDragPboard];
		[thePboard declareTypes:&MHMenuCellPboardType num:1 owner:self];
		
		// Write the cell to the pasteboard
		if (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) 
		{
	    	if (ts = NXOpenTypedStream(stream, NX_WRITEONLY)) 
			{
				NXWriteRootObject(ts, theCell);
				NXCloseTypedStream(ts);
	    	}
	   		[thePboard writeType:MHMenuCellPboardType fromStream:stream];
	    	NXCloseMemory(stream, NX_FREEBUFFER);
		}
   
   		if (!cursor)
		{
			NXImage *cursorImage = [NXImage findImageNamed:"lightningCursor"];
			if (cursorImage)
			{
				cursor = [[NXCursor alloc] initFromImage:cursorImage];
				spot.x = 8.0; spot.y = 8.0;
				[cursor setHotSpot:&spot];
			}
		}
		if (cursor)
			[cursor push];
			
		// Start the drag
		[self dragImage:theImage	// visible on screen during drag
			at:&offset 				// offset of the mouse point for the drag
			offset:&zero			// offset of the inital mouse pt
			event:theEvent			// theEvent structure
			pasteboard:thePboard	// a Pasteboard with data on it
			source:self				// source object
			slideBack:NO];			// if no destination don't animate back to source - make a new menu
		
		if (cursor)
			[cursor pop];

		[theImage free];
	}
	else
	{
		[super mouseDown:theEvent];
	}
    return self;
}

// Methods to be the source of a dragging operation
- draggedImage:(NXImage *)image beganAt:(NXPoint *)screenPoint
{
	return self;
}

- (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
{
    return (NX_DragOperationCopy | NX_DragOperationGeneric);
}

// Make a new menu if the user dropped the menu item anywhere not on a CustomMenu
- draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint deposited:(BOOL)flag
{
	if (!flag)	// They just dragged it out into thin air
	{
		int retval;
		retval = NXRunAlertPanel("Custom Menu", "Would you like to create a new custom menu?", "Yes", "No", NULL);
		
		if (retval == NX_ALERTDEFAULT)	// Yes make a new one
		{
			// Check whether there are any other custom menus already defined
			
			// This will have to be changed if you are asking the users for a menu name
			int i, count = 1;
			id draggedCell = [matrix selectedCell];
			char titleString[128];
			id newMenu;
			id newItem;
			id mainItemList = [[NXApp mainMenu] itemList];
			id itemList;
			List *menuAndItemList = [[List alloc] init];
			
			for (i = 0; i < [mainItemList cellCount]; i++)
			{
				id thisCell = [mainItemList cellAt:i :0];
				// We have given the menus we have added a tag of 42131 to identify them
				if(strstr([thisCell title], "Custom Menu") && ([thisCell tag] == 42131))
				{
					count++;
				}
			}
			
			sprintf(titleString, "Custom Menu %d", count);
			
			// Create a new menu in the main menu
			newItem = [[MenuCell alloc] initTextCell:titleString];
			[newItem setTag:42131];
			[mainItemList insertRowAt:count-1];
			[mainItemList putCell:newItem at:count-1 :0];
			[menuAndItemList addObject:mainItemList];
			[menuAndItemList addObject:newItem];
			
			// Create the new submenu
			newMenu = [[CustomMenu alloc] initTitle:titleString];
			
			// Add the dragged menu item to the new menu
			itemList = [newMenu itemList];
			[itemList insertRowAt:0];
			[itemList putCell:draggedCell at:0 :0];
			[newMenu setItemList:itemList];
			
			// Attach the new submenu to the main menu
			[menuAndItemList addObject:newMenu];
			
			// This is done in this way to avoid removing the view from the view hierarchy while lockfocused.
			[self perform:@selector(attachSubmenuUsing:) with:menuAndItemList afterDelay:0 cancelPrevious:YES];
		}
	}
    return self;
}

- (BOOL)ignoreModifierKeysWhileDragging
{
	return YES;
}

- (BOOL)isDraggingSourceLocal
{
	return YES;
}

// Method to avoid getting DPS errors - see draggedImage:endedAt:deposited:
- attachSubmenuUsing:aList
{
	id itemList = [aList objectAt:0];
	id newItem = [aList objectAt:1];
	id newMenu = [aList objectAt:2];
	
	[[NXApp mainMenu] setItemList:itemList];
	[[NXApp mainMenu] display];
	
	// Attach the new submenu to the main menu
	[[NXApp mainMenu] setSubmenu:newMenu forItem:newItem];
	
	[aList free];
	
	return self;
}

- write:(NXTypedStream *)stream
{
    [super write:stream];
	NXWriteType(stream, "c", &__saveAndRestoreFlag__);
    return self;
}

- read:(NXTypedStream *)stream
{
    [super read:stream];
	NXReadType(stream, "c", &__saveAndRestoreFlag__);

	// The poseas has to happen at this point so the read and write methods work, with the right super-class
	if (![NXApp respondsTo:@selector(isTestingInterface)])	// We are not in IB
			[[DraggableMenu class] poseAs:[Menu class]];
	return self;
}

@end
